#region

using System;
using System.Collections.Generic;
using System.Linq;
using SFML.Window;
using SFMLStart.Utilities;
using VeeTileEngine2012;

#endregion

namespace TimeDRODPOF.TDComponents
{
    public class TDCMovement : Component
    {
        #region MovementType enum

        public enum MovementType
        {
            Direct,
            BeelineNormal,
            BeelineSmart,
            FlexibleNormal
        }

        #endregion

        private bool _success;

        public TDCMovement(MovementType mMovement, string[] mAllowedTags, string[] mObstacleTags, string[] mExceptionTags)
        {
            IgnoreEntities = new List<Entity>();
            ObstaclePositions = new List<Vector2i>();

            Movement = mMovement;
            AllowedTags = mAllowedTags;
            ObstacleTags = mObstacleTags;
            ExceptionTags = mExceptionTags;
        }

        public MovementType Movement { get; set; }
        public int NextX { get; private set; }
        public int NextY { get; private set; }
        public int TargetX { get; set; }
        public int TargetY { get; set; }

        public bool IsReverse { get; set; }
        public bool PrefersHorizontal { get; set; }
        public string[] AllowedTags { get; set; }
        public string[] ObstacleTags { get; set; }
        public string[] ExceptionTags { get; set; }
        public List<Entity> IgnoreEntities { get; set; }
        public List<Vector2i> ObstaclePositions { get; set; }
        public Action OnMovementFail { get; set; }
        public Action OnMovementSuccess { get; set; }
        public Action<int, int> OnMovedOutsideBounds;

        private void InvokeOnMovedOutsideBounds(int mX, int mY)
        {
            var handler = OnMovedOutsideBounds;
            if (handler != null) handler(mX, mY);
        }

        private void TryMovement()
        {
            switch (Movement)
            {
                case MovementType.Direct:
                    DirectMovement();
                    break;
                case MovementType.BeelineNormal:
                    BeelineNormalMovement();
                    break;
                case MovementType.BeelineSmart:
                    BeelineSmartMovement();
                    break;
                case MovementType.FlexibleNormal:
                    FlexibleNormalMovement();
                    break;
            }
        }

        private bool CheckSpecialSquares(bool mNextSuccess)
        {
            var abort = false;

            ObstaclePositions.Clear();

            foreach (var component in new List<TDCSpecialSquare>(Field.GetComponents(X, Y, typeof (TDCSpecialSquare)).Cast<TDCSpecialSquare>()))
                if (!component.InvokeOnMoveFromAllowed(Entity, NextX, NextY, mNextSuccess, out abort))
                    ObstaclePositions.Add(new Vector2i(X + NextX, Y + NextY));

            if (Field.IsTileValid(X + NextX, Y + NextY))
                foreach (var component in new List<TDCSpecialSquare>(Field.GetComponents(X + NextX, Y + NextY, typeof (TDCSpecialSquare)).Cast<TDCSpecialSquare>()))
                    if (!component.InvokeOnMoveToAllowed(Entity, NextX, NextY, mNextSuccess, out abort))
                        ObstaclePositions.Add(new Vector2i(X + NextX, Y + NextY));

            return abort;
        }

        private void DirectMovement()
        {
            var nextSuccess = IsNextAllowed(Field, X + NextX, Y + NextY, AllowedTags);
            if (IsNextObstacle(Field, X + NextX, Y + NextY, ObstacleTags, IgnoreEntities, ObstaclePositions, ExceptionTags)) nextSuccess = false;
            if (CheckSpecialSquares(nextSuccess)) nextSuccess = false;
            if (IsNextObstaclePosition(ObstaclePositions, X + NextX, Y + NextY)) nextSuccess = false;

            _success = nextSuccess;
        }

        private void BeelineNormalMovement()
        {
            var xInts = new int[3];
            var yInts = new int[3];

            xInts[0] = NextX;
            yInts[0] = NextY;

            if (!PrefersHorizontal)
            {
                xInts[1] = 0;
                yInts[1] = NextY;
                xInts[2] = NextX;
                yInts[2] = 0;
            }
            else
            {
                xInts[1] = NextX;
                yInts[1] = 0;
                xInts[2] = 0;
                yInts[2] = NextY;
            }

            BeelineCommonMovement(xInts, yInts);
        }

        private void BeelineCommonMovement(IList<int> xInts, IList<int> yInts)
        {
            for (var i = 0; i < 3; i++)
            {
                var nextSuccess = true;
                NextX = xInts[i];
                NextY = yInts[i];
                if (!IsNextAllowed(Field, X + NextX, Y + NextY, AllowedTags))
                    nextSuccess = false;
                if (IsNextObstacle(Field, X + NextX, Y + NextY, ObstacleTags, IgnoreEntities, ObstaclePositions, ExceptionTags))
                    nextSuccess = false;
                if (CheckSpecialSquares(nextSuccess))
                {
                    _success = false;
                    break;
                }
                if (IsNextObstaclePosition(ObstaclePositions, X + NextX, Y + NextY))
                    nextSuccess = false;
                _success = nextSuccess;
                if (_success)
                    break;
            }
        }

        private void BeelineSmartMovement()
        {
            var xInts = new int[3];
            var yInts = new int[3];

            xInts[0] = NextX;
            yInts[0] = NextY;

            if (Math.Abs(X - TargetX) >=
                Math.Abs(Y - TargetY))
            {
                xInts[1] = 0;
                yInts[1] = NextY;
                xInts[2] = NextX;
                yInts[2] = 0;
            }
            else
            {
                xInts[1] = NextX;
                yInts[1] = 0;
                xInts[2] = 0;
                yInts[2] = NextY;
            }

            BeelineCommonMovement(xInts, yInts);
        }

        private void FlexibleNormalMovement()
        {
            var xInts = new int[3];
            var yInts = new int[3];

            xInts[0] = NextX;
            yInts[0] = NextY;

            if (NextX != 0 &&
                NextY != 0)
            {
                if (Math.Abs(X - TargetX) >=
                    Math.Abs(Y - TargetY))
                {
                    xInts[1] = 0;
                    yInts[1] = NextY;
                    xInts[2] = NextX;
                    yInts[2] = 0;
                }
                else
                {
                    xInts[1] = NextX;
                    yInts[1] = 0;
                    xInts[2] = 0;
                    yInts[2] = NextY;
                }
            }
            else if (NextX == 0)
            {
                if (NextY == -1)
                {
                    xInts[1] = -1;
                    yInts[1] = -1;
                    xInts[2] = 1;
                    yInts[2] = -1;
                }
                else if (NextY == 1)
                {
                    xInts[1] = -1;
                    yInts[1] = 1;
                    xInts[2] = 1;
                    yInts[2] = 1;
                }
            }
            else if (NextY == 0)
            {
                if (NextX == -1)
                {
                    xInts[1] = -1;
                    yInts[1] = -1;
                    xInts[2] = -1;
                    yInts[2] = 1;
                }
                else if (NextX == 1)
                {
                    xInts[1] = 1;
                    yInts[1] = -1;
                    xInts[2] = 1;
                    yInts[2] = 1;
                }
            }

            BeelineCommonMovement(xInts, yInts);
        }

        public static Vector2i GetNextXY(Entity mEntity, int mTargetX, int mTargetY)
        {
            int nextX = 0, nextY = 0;

            if (mEntity.X < mTargetX) nextX = 1;
            else if (mEntity.X > mTargetX) nextX = -1;

            if (mEntity.Y < mTargetY) nextY = 1;
            else if (mEntity.Y > mTargetY) nextY = -1;

            return new Vector2i(nextX, nextY);
        }

        private static bool TileHasEntityIgnore(Field mField, int mX, int mY, string mTag, IEnumerable<Entity> mIgnoreEntities, IEnumerable<string> mExceptionTags)
        {
            var tempTile = mField.GetTile(mX, mY);
            if (tempTile == null) return false;

            if (mIgnoreEntities == null) mIgnoreEntities = new Entity[] {};
            if (mExceptionTags == null) mExceptionTags = new string[] {};

            return mField.GetEntitiesByTag(mX, mY, mTag).Where(entity => !mExceptionTags.Any(entity.HasTag)).Any(entity => !mIgnoreEntities.Contains(entity));
        }

        public static bool IsNextAllowed(Field mField, int mX, int mY, IEnumerable<string> mAllowedTags) { return mField.IsTileValid(mX, mY) && mAllowedTags.Any(x => mField.HasEntityByTag(mX, mY, x)); }

        public static bool IsNextObstacle(Field mField, int mX, int mY, IEnumerable<string> mObstacleTags,
                                          IEnumerable<Entity> mIgnoreEntities, IEnumerable<Vector2i> mObstaclePositions, IEnumerable<string> mExceptionTags) { return mObstacleTags.Any(x => TileHasEntityIgnore(mField, mX, mY, x, mIgnoreEntities, mExceptionTags)); }

        public static bool IsNextObstaclePosition(IEnumerable<Vector2i> mObstaclePositions, int mX, int mY)
        {
            return mObstaclePositions != null &&
                   mObstaclePositions.Any(vector => vector.X == mX && vector.Y == mY);
        }

        public override void Refresh()
        {
            base.Refresh();

            ObstaclePositions.Clear();
            _success = true;
            TargetX = X;
            TargetY = Y;
            NextX = NextY = 0;
        }

        public override void NextTurn(object mInput)
        {
            base.NextTurn(mInput);

            if (TargetX == X && TargetY == Y) return;

            var nextXY = GetNextXY(Entity, TargetX, TargetY);
            var firstNextX = NextX = nextXY.X;
            var firstNextY = NextY = nextXY.Y;

            if (IsReverse)
            {
                NextX *= -1;
                NextY *= -1;
            }

            if (NextX == 0 && NextY == 0) return;

            TryMovement();

            if (_success)
            {
                Entity.Move(X + NextX, Y + NextY);
                OnMovementSuccess.SafeInvoke();
            }
            else
            {
                OnMovementFail.SafeInvoke();
                if (!Field.IsTileValid(X + firstNextX, Y + firstNextY))
                    InvokeOnMovedOutsideBounds(X + firstNextX, Y + firstNextY);
            }
        }
    }
}